//
//  ZXDownloadManager.m
//  ZXDownload
//
//  Created by xin on 2018/5/3.
//  Copyright © 2018年 魏鑫. All rights reserved.
//

#import "ZXDownloadManager.h"
#import "ZXDownloadSupportUtils.h"

@interface ZXDownloadManager () <NSURLSessionDataDelegate>

@property (nonatomic, strong) dispatch_queue_t synchronizationQueue;
@property (nonatomic, strong) NSURLSession *session;

@property (nonatomic, assign) NSInteger maximumActiveDownloads;
@property (nonatomic, assign) NSInteger activeRequestCount;

@property (nonatomic, strong) NSMutableArray *queuedTasks;
@property (nonatomic, strong) NSMutableDictionary *tasks;

@property (nonatomic, strong) NSMutableDictionary *allDownloadFileModels;
@property (nonatomic, assign) UIBackgroundTaskIdentifier backgroundTaskId;

@end

@implementation ZXDownloadManager

+ (nonnull instancetype)sharedManager {
    static dispatch_once_t once;
    static ZXDownloadManager * instance = nil;
    dispatch_once(&once, ^{
        instance = [[self alloc] init];
    });
    return instance;
}

+ (NSURLSessionConfiguration *)defaultURLSessionConfiguration {
    NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration defaultSessionConfiguration];
    configuration.HTTPShouldSetCookies = YES;
    configuration.HTTPShouldUsePipelining = NO;//请求得到响应后再请求，如果为YES，不用等待响应即可发出请求，可能会出现新的请求收到旧的响应的问题
    configuration.requestCachePolicy = NSURLRequestUseProtocolCachePolicy;
    configuration.allowsCellularAccess = YES;//允许蜂窝网络请求
    configuration.timeoutIntervalForRequest = 60;
    configuration.HTTPMaximumConnectionsPerHost = 10;
    configuration.discretionary = YES;//允许系统裁量最佳性能安排任务，在后台模式自动开启
    return configuration;
}

- (instancetype)init {
    
//    NSURLSessionConfiguration *defaultConfiguration = [self.class defaultURLSessionConfiguration];
    NSURLSessionConfiguration *defaultConfiguration = [NSURLSessionConfiguration defaultSessionConfiguration];
    
    NSOperationQueue *queue = [[NSOperationQueue alloc] init];
    queue.maxConcurrentOperationCount = 1;
    NSURLSession *session = [NSURLSession sessionWithConfiguration:defaultConfiguration delegate:self delegateQueue:queue];
    
    return [self initWithSession:session maximumActiveDownloads:4];
}

- (nonnull instancetype)initWithSession:(NSURLSession *)session maximumActiveDownloads:(NSInteger)maximumActiveDownloads  {
    if (self = [super init]) {
        
        self.session = session;
        self.maximumActiveDownloads = maximumActiveDownloads;
        
        self.queuedTasks = [[NSMutableArray alloc] init];
        self.tasks = [[NSMutableDictionary alloc] init];
        self.activeRequestCount = 0;
        
        NSString *queueName = [NSString stringWithFormat:@"com.ZXDownloadManager.synchronizationQueue-%@",[[NSUUID UUID] UUIDString]];
        self.synchronizationQueue = dispatch_queue_create([queueName cStringUsingEncoding:NSASCIIStringEncoding], DISPATCH_QUEUE_SERIAL);
        
        [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(applicationWillTerminate:) name:UIApplicationWillTerminateNotification object:nil];
        [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(applicationDidReceiveMemoryWarning:) name:UIApplicationDidReceiveMemoryWarningNotification object:nil];
        [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(applicationWillResignActive:) name:UIApplicationWillResignActiveNotification object:nil];
        [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(applicationDidBecomeActive:) name:UIApplicationDidBecomeActiveNotification object:nil];
    }
    return self;
}

- (ZXDownloadFile *)downloadFileWithURL:(NSString *)url progress:(void (^)(NSProgress * progress,ZXDownloadFile * file))progress success:(void (^)(NSURL *url))success failure:(void (^)(NSError *error))failure {
    
    __block ZXDownloadFile *file = [self getDownloadFileForURL:url];
    
    dispatch_sync(self.synchronizationQueue, ^{
        NSString *URLIdentifier = url;
        if (URLIdentifier == nil) {
            if (failure) {
                NSError *error = [NSError errorWithDomain:NSURLErrorDomain code:NSURLErrorBadURL userInfo:nil];
                dispatch_async(dispatch_get_main_queue(), ^{
                    JPErrorLog(@"%@",error);
                    failure(error);
                });
            }
            return ;
        }
        
        file.zx_successBlock = success;
        file.zx_failureBlock = failure;
        file.zx_progressBlock = progress;
        
        if (file.state == ZXDownloadFileStateCompleted && file.totalBytesExpectedToWrite == file.totalBytesWritten) {
            dispatch_async(dispatch_get_main_queue(), ^{
                if (file.zx_successBlock) {
                    file.zx_successBlock([NSURL URLWithString:file.fileURL]);
                }
            });
            return ;
        }
        
        if (file.state == ZXDownloadFileStateDownloading && file.totalBytesWritten != file.totalBytesExpectedToWrite) {
            dispatch_async(dispatch_get_main_queue(), ^{
                if (file.zx_progressBlock) {
                    file.zx_progressBlock(file.progress, file);
                }
            });
        }
        
        NSURLSessionDataTask *task = self.tasks[file.fileURL];
        if (!task || ((task.state != NSURLSessionTaskStateRunning) && (task.state != NSURLSessionTaskStateSuspended))) {
            NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:file.fileURL]];
            //从多少字节开始请求
            NSString *range = [NSString stringWithFormat:@"btye=%lld-",file.totalBytesWritten];
            [request setValue:range forHTTPHeaderField:@"Range"];
            
            NSURLSessionDataTask *task = [self.session dataTaskWithRequest:request];
            task.taskDescription = file.fileURL;
            self.tasks[file.fileURL] = task;
            [self.queuedTasks addObject:task];
        }
        
        [self resumeWithDownloadFile:file];
    });
    
    return file;
}

#pragma mark - ZXDownloadFile Data Method

- (NSMutableDictionary *)allDownloadFileModels {
    if (_allDownloadFileModels == nil) {
        NSDictionary *files = [NSKeyedUnarchiver unarchiveObjectWithFile:[ZXDownloadSupportUtils LocalReceiptsPath]];
        _allDownloadFileModels = files != nil ? files.mutableCopy : [NSMutableDictionary dictionary];
    }
    return _allDownloadFileModels;
}

- (void)saveDownloadFiles:(NSDictionary *)downloadFiles {
    [NSKeyedArchiver archiveRootObject:downloadFiles toFile:[ZXDownloadSupportUtils LocalReceiptsPath]];
}

- (ZXDownloadFile *)updateDownloadFileWithURL:(NSString *)url state:(ZXDownloadFileState)state {
    ZXDownloadFile * file = [self getDownloadFileForURL:url];
    file.state = state;
    
    [self saveDownloadFiles:self.allDownloadFileModels];
    return file;
}

#pragma mark - ZXDownloadControlProtocol

/**
 恢复下载

 @param file 下载file对象
 */
- (void)resumeWithDownloadFile:(ZXDownloadFile *)file {
    if ([self isActiveReuqestCountBelowMaximumLimit]) {
        NSURLSessionDataTask *task = self.tasks[file.fileURL];
        if (!task || (task.state != NSURLSessionTaskStateRunning && (task.state != NSURLSessionTaskStateSuspended))) {
            [self downloadFileWithURL:file.fileURL progress:file.zx_progressBlock success:file.zx_successBlock failure:file.zx_failureBlock];
        } else {
            [self startTask:self.tasks[file.fileURL]];
            file.date = [NSDate date];
        }
    } else {
        file.state = ZXDownloadFileStateResume;
        [self saveDownloadFiles:self.allDownloadFileModels];
        [self.queuedTasks addObject:self.tasks[file.fileURL]];
    }
}

/**
 *  暂停全部
 */
- (void)suspendAll {
    for (NSURLSessionDataTask *task in self.queuedTasks) {
        ZXDownloadFile * file = [self getDownloadFileForURL:task.taskDescription];
        file.state = ZXDownloadFileStateFailed;
        [task suspend];
        [self decrementActiveTaskCount];
    }
    [self saveDownloadFiles:self.allDownloadFileModels];
}

/**
 *  暂停一个下载任务

 @param url 下载任务的url地址
 */
- (void)suspendWithURL:(NSString *)url {
    if (url == nil) return;
    ZXDownloadFile * file = [self getDownloadFileForURL:url];
    [self suspendWithDownloadFile:file];
}

/**
 *  暂停一个下载任务

 @param file 下载任务的file对象
 */
- (void)suspendWithDownloadFile:(ZXDownloadFile * _Nonnull)file {
    [self updateDownloadFileWithURL:file.fileURL state:ZXDownloadFileStateSuspened];
    NSURLSessionDataTask *task = self.tasks[file.fileURL];
    if (task) {
        [task suspend];
        [self decrementActiveTaskCount];
        [self startNextTaskIfNecessary];
    }
}

/**
 删除一个下载任务

 @param url 删除任务的url地址
 */
- (void)removeWithURL:(NSString * _Nonnull)url {
    if (url == nil) return;
    ZXDownloadFile *file = [self getDownloadFileForURL:url];
    [self removeWithDownloadFile:file];
}

/**
 删除一个下载任务

 @param file 删除任务的file对象
 */
- (void)removeWithDownloadFile:(ZXDownloadFile * _Nonnull)file {
    NSURLSessionDataTask *task = self.tasks[file.fileURL];
    if (task) {
        [task cancel];
    }
    
    [self.queuedTasks removeObject:task];
    [self removeTaskWithURLIdentifier:file.fileURL];
    
    dispatch_sync(self.synchronizationQueue, ^{
        [self.allDownloadFileModels removeObjectForKey:file.fileURL];
        [self saveDownloadFiles:self.allDownloadFileModels];
    });
    
    //删除下载文件
    NSFileManager *fileManager = [NSFileManager defaultManager];
    [fileManager removeItemAtPath:file.filePath error:nil];
}

#pragma mark - task Method

/**
 *  移除下载任务
 */
- (NSURLSessionDataTask *)removeTaskWithURLIdentifier:(NSString *)URLIdentifier {
    __block NSURLSessionDataTask *task = nil;
    //同步执行队列
    dispatch_sync(self.synchronizationQueue, ^{
        task = self.tasks[URLIdentifier];
        [self.tasks removeObjectForKey:URLIdentifier];
    });
    return task;
}

/**
 *  递减活动的任务数量
 */
- (void)decrementActiveTaskCount {
    dispatch_sync(self.synchronizationQueue, ^{
        if (self.activeRequestCount > 0) {
            self.activeRequestCount -= 1;
        }
    });
}

/**
 *  如果可能开始执行下一个任务，判断下载任务 < 支持下载任务最大值 就开启下一个任务
 */
- (void)startNextTaskIfNecessary {
    dispatch_sync(self.synchronizationQueue, ^{
        if ([self isActiveReuqestCountBelowMaximumLimit]) {
            while (self.queuedTasks.count > 0) {
                NSURLSessionDataTask *task = [self dequeueTask];
                ZXDownloadFile *file = [self getDownloadFileForURL:task.taskDescription];
                if (task.state == NSURLSessionTaskStateSuspended && file.state == ZXDownloadFileStateResume) {
                    [self startTask:task];
                    break;
                }
            }
        }
    });
}

/**
 *  判断 下载任务 < 支持下载任务最大值
 */
- (BOOL)isActiveReuqestCountBelowMaximumLimit {
    return self.activeRequestCount < self.maximumActiveDownloads;
}

/**
 *  开始下载任务

 @param task 需要开始的任务
 */
- (void)startTask:(NSURLSessionDataTask *)task {
    [task resume];
    ++self.activeRequestCount;
    [self updateDownloadFileWithURL:task.taskDescription state:ZXDownloadFileStateDownloading];
}

/**
 *  取等待队列的第一个任务
 */
- (NSURLSessionDataTask *)dequeueTask {
    NSURLSessionDataTask * task = nil;
    task = [self.queuedTasks firstObject];
    [self.queuedTasks removeObject:task];
    return task;
}

#pragma mark - url method

/**
 *  通过url下载地址获取file对象
 */
- (ZXDownloadFile *)getDownloadFileForURL:(NSString *)url {
    if (url == nil) {
        NSLog(@"下载文件的url不能为空");
        return nil;
    }
    
    ZXDownloadFile *file = self.allDownloadFileModels[url];
    if (file) {
        return file;
    }
    
    file = [[ZXDownloadFile alloc] initWithURL:url];
    file.state = ZXDownloadFileStateNone;
    file.totalBytesExpectedToWrite = 1;
    
    dispatch_sync(self.synchronizationQueue, ^{
        [self.allDownloadFileModels setObject:file forKey:url];
        [self saveDownloadFiles:self.allDownloadFileModels];
    });
    
    return file;
}

/**
 否在本地有过下载

 @param URL 文件下载地址
 @return 文件否是在本地有过下载
 */
- (BOOL)isLocalDownloadFileWithURL:(NSURL *)URL {
    return YES;
}

#pragma mark - NSNotification

- (void)applicationWillTerminate:(NSNotification *)not {
    [self suspendAll];
}

- (void)applicationDidReceiveMemoryWarning:(NSNotification *)not {
    [self suspendAll];
}

- (void)applicationWillResignActive:(NSNotification *)not {
    //失去激活
    Class UIApplicationClass = NSClassFromString(@"UIApplication");
    BOOL hasApplication = UIApplicationClass && [UIApplicationClass respondsToSelector:@selector(sharedApplication)];
    if (hasApplication) {
        __weak __typeof__ (self) wself = self;
        UIApplication *app = [UIApplicationClass performSelector:@selector(sharedApplication)];
        self.backgroundTaskId = [app beginBackgroundTaskWithExpirationHandler:^{
            __strong __typeof (wself) sself = wself;
            if (sself) {
                [sself suspendAll];
                
                [app endBackgroundTask:sself.backgroundTaskId];
                sself.backgroundTaskId = UIBackgroundTaskInvalid;
            }
        }];
    }
}


- (void)applicationDidBecomeActive:(NSNotification *)not {
    Class UIApplicationClass = NSClassFromString(@"UIApplication");
    if (!UIApplicationClass || ![UIApplicationClass respondsToSelector:@selector(sharedApplication)]) {
        return;
    }
    if (self.backgroundTaskId != UIBackgroundTaskInvalid) {
        UIApplication * app = [UIApplication performSelector:@selector(sharedApplication)];
        [app endBackgroundTask:self.backgroundTaskId];
        self.backgroundTaskId = UIBackgroundTaskInvalid;
    }
}


#pragma mark - NSURLSessionDataDelegate

- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask
didReceiveResponse:(NSURLResponse *)response
 completionHandler:(void (^)(NSURLSessionResponseDisposition disposition))completionHandler {
    ZXDownloadFile *file = [self getDownloadFileForURL:dataTask.taskDescription];
    file.totalBytesExpectedToWrite = file.totalBytesWritten + dataTask.countOfBytesExpectedToReceive;
    file.state = ZXDownloadFileStateDownloading;
    NSLog(@"url=%@",dataTask.taskDescription);
    NSLog(@"dataTask=%lld",dataTask.countOfBytesExpectedToReceive);
    [file fileInfo];
    [self saveDownloadFiles:self.allDownloadFileModels];
    
    completionHandler(NSURLSessionResponseAllow);
}

- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask
    didReceiveData:(NSData *)data {
    NSLog(@"didReceiveData=%lu",(unsigned long)data.length);
    
    dispatch_sync(self.synchronizationQueue, ^{
       
        __block NSError *error = nil;
        ZXDownloadFile *file = [self getDownloadFileForURL:dataTask.taskDescription];
        
        //速度
        file.totalRead += data.length;
        NSDate *currentDate = [NSDate date];
        if ([currentDate timeIntervalSinceDate:file.date] >= 1) {
            double time = [currentDate timeIntervalSinceDate:file.date];
            long long speed = file.totalRead / time;
            file.speed = [file customFormatByteCount:speed];
            file.totalRead = 0.0;
            file.date  = currentDate;
        }
        
        //写入数据 每次收到的数据都写入问本地文件中
        NSInputStream *inputStream = [[NSInputStream alloc] initWithData:data];
        NSOutputStream *outputStream = [[NSOutputStream alloc] initWithURL:[NSURL fileURLWithPath:file.filePath] append:YES];
        [inputStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
        [outputStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
        [inputStream open];
        [outputStream open];
        
        while ([inputStream hasBytesAvailable] && [outputStream hasSpaceAvailable]) {
            uint8_t buffer[1024];
            
            NSInteger bytesRead = [inputStream read:buffer maxLength:1024];
            if (inputStream.streamError || bytesRead < 0) {
                error = inputStream.streamError;
                break;
            }
            
            NSInteger bytesWritten = [outputStream write:buffer maxLength:(NSUInteger)bytesRead];
            if (outputStream.streamError || bytesWritten < 0) {
                error = outputStream.streamError;
                break;
            }
            
            if (bytesRead == 0 && bytesWritten == 0) {
                break;
            }
        }
        [outputStream close];
        [inputStream close];
        
        //更新下载进度
        file.progress.totalUnitCount = file.totalBytesExpectedToWrite;
        file.progress.completedUnitCount = file.totalBytesWritten;
        
        dispatch_async(dispatch_get_main_queue(), ^{
            if (file.zx_progressBlock) {
                file.zx_progressBlock(file.progress, file);
            }
        });
        
    });
}

- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task
didCompleteWithError:(nullable NSError *)error {
    ZXDownloadFile *file = [self getDownloadFileForURL:task.taskDescription];
    
    if (error) {
        file.state = ZXDownloadFileStateFailed;
        dispatch_async(dispatch_get_main_queue(), ^{
            //错误通知
            if (file.zx_failureBlock) {
                file.zx_failureBlock(error);
            }
        });
    }else {
        [file.stream close];
        file.stream = nil;
        file.state = ZXDownloadFileStateCompleted;
        dispatch_async(dispatch_get_main_queue(), ^{
           //下载完成的通知
            if (file.zx_successBlock) {
                file.zx_successBlock(task.originalRequest.URL);
            }
        });
    }
    
    [self saveDownloadFiles:self.allDownloadFileModels];
    [self decrementActiveTaskCount];
    [self startNextTaskIfNecessary];
}

@end
